package org.elsfs.cloud.common.mybatis.ext.sql;


import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.commons.compress.utils.Lists;
import org.elsfs.cloud.common.core.entity.BaseEntity;
import org.elsfs.cloud.common.mybatis.ext.sql.support.insert.StandardInsertIntoItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.insert.StandardValuesItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.CompositeSetItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.ExpressionSetItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.SetItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.StandardSetItem;
import org.elsfs.cloud.common.util.lang.CollectionUtils;
import org.elsfs.cloud.common.util.lang.NamingCase;
import org.elsfs.cloud.common.util.lang.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * 抽象插入SQL实现
 *
 * @param <C> 子类具体类型

 */
@SuppressWarnings("unchecked")
public abstract class AbstractInsertSql<C extends AbstractInsertSql<C>> extends CompositeSqlFragment implements InsertSql<C>, SqlParameterManagerAware {

    /**
     * 具体子类实现引用
     */
    protected final C childThis = (C) this;

    /**
     * 插入表
     */
    private String table;
    /**
     * 插入字段
     */
    private Collection<String> insertFields = Collections.emptyList();
    /**
     * 冲突约束名称
     */
    private String conflictConstraint;
    /**
     * 插入值集合
     */
    private final Collection<Map<String, Object>> values = Lists.newArrayList();
    /**
     * 存在冲突处理
     */
    private boolean hasConflictHandling;
    /**
     * 冲突更新SQL
     */
    private ConflictUpdateSql<C> conflictUpdateSql;
    /**
     * 忽略冲突
     */
    private boolean ignoreConflict;

    /**
     * 插入的表
     *
     * @param entityClass 实体类
     * @return 具体实现
     */
    @Override
    public C insertInto(Class<? extends BaseEntity> entityClass) {
        this.table = TableInfoHelper.getTableInfo(entityClass).getTableName();

        return this.childThis;
    }

    /**
     * 插入的表
     *
     * @param table 表名
     * @return 具体实现
     */
    @Override
    public C insertInto(String table) {
        this.table = table;

        return this.childThis;
    }

    /**
     * 插入的字段
     *
     * @param fields 字段名称
     * @return 具体实现
     */
    @Override
    public C fields(String... fields) {
        this.insertFields = Arrays.asList(fields);

        return this.childThis;
    }

    /**
     * 插入的字段
     *
     * @param fields 字段名称枚举列表
     * @return 具体实现
     */
    @Override
    public C fields(Enum<?>... fields) {
        this.insertFields = Arrays.stream(fields)
            .map(Enum::name)
            .toList();

        return this.childThis;
    }

    /**
     * 插入的字段
     *
     * @param fields 字段名称
     * @return 具体实现
     */
    @Override
    public C fields(Collection<String> fields) {
        this.insertFields = new ArrayList<>(fields);

        return this.childThis;
    }

    /**
     * 插入的字段
     *
     * @param fields 字段名称枚举集合
     * @return 具体实现
     */
    @Override
    public C fieldsEnum(Collection<Enum<?>> fields) {
        this.insertFields = fields.stream()
            .map(Enum::name)
            .toList();

        return this.childThis;
    }

    /**
     * 插入的值
     *
     * @param value 值Map
     * @return 具体实现
     */
    @Override
    public C values(Map<String, Object> value) {
        if (value == null) {
            throw new MybatisPlusException( "插入值Map不能为空");
        }
        this.values.add(value);

        return this.childThis;
    }

    /**
     * 批量插入的值集合
     *
     * @param values 值集合
     * @return 具体实现
     */
    @Override
    public C values(Collection<Map<String, Object>> values) {
        if (CollectionUtils.isEmpty(values)) {
            throw new MybatisPlusException("插入值Map集合不能为空");
        }
        this.values.addAll(values);

        return this.childThis;
    }

    /**
     * 插入冲突时
     *
     * @param constraintName 冲突的约束名称
     * @return 具体实现
     */
    @Override
    public C onConflict(String constraintName) {
        this.conflictConstraint = constraintName;
        this.hasConflictHandling = true;

        return this.childThis;
    }

    /**
     * 冲突时啥也不干
     *
     * @return 具体实现
     */
    @Override
    public C doNothing() {
        this.ignoreConflict = true;

        return this.childThis;
    }

    /**
     * 冲突时执行更新语句
     *
     * @return 冲突更新SQl
     */
    @Override
    public ConflictUpdateSql<C> doUpdate() {
        this.ignoreConflict = false;

        this.conflictUpdateSql = new ConflictUpdateSqlImpl();
        return this.conflictUpdateSql;
    }

    /**
     * 构建SQL之前处理
     */
    @Override
    protected void beforeBuild() {
        this.addSqlFragment(
            new StandardInsertIntoItem(this.table, this.insertFields)
        );
        this.addSqlFragment(
            new StandardValuesItem(this.getSqlParameterManager(), this.insertFields, this.values)
        );

        if (this.hasConflictHandling) {
            if (StringUtils.isBlank(this.conflictConstraint)) {
                throw new MybatisPlusException( "UPSERT语句冲突的约束名称不能为空");
            }
            this.addSqlFragment(
                new TokenSqlFragment(
                    "ON CONFLICT ON CONSTRAINT " + this.conflictConstraint
                )
            );

            if (this.ignoreConflict) {
                this.addSqlFragment(
                    new TokenSqlFragment("DO NOTHING")
                );
            } else {
                this.addSqlFragment(
                    new TokenSqlFragment("DO UPDATE")
                );
                this.addSqlFragment(
                    this.conflictUpdateSql
                );
            }
        }
    }

    /**
     * 冲突更新SQl实现
     */
    public class ConflictUpdateSqlImpl implements ConflictUpdateSql<C> {

        /**
         * 冲突值中的字段前缀
         */
        private static final String EXCLUDE_FIELD_PREFIX = "excluded.";

        /**
         * 更新设置项
         */
        private final Collection<SetItem> setItems = Lists.newArrayList();

        /**
         * 返回上一级对象
         *
         * @return 上级对象
         */
        @Override
        public C end() {
            return AbstractInsertSql.this.childThis;
        }

        /**
         * 设置字段表达式
         *
         * @param field         字段名称
         * @param excludedField 冲突值中的字段名称
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> setConflict(String field, String excludedField) {
            this.setItems.add(
                new ExpressionSetItem(
                    field,
                    EXCLUDE_FIELD_PREFIX + NamingCase.toUnderlineCase(excludedField),
                    null
                )
            );

            return this;
        }

        /**
         * 设置字段表达式
         *
         * @param field         字段枚举
         * @param excludedField 冲突值中的字段名称
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> setConflict(Enum<?> field, Enum<?> excludedField) {
            this.setItems.add(
                new ExpressionSetItem(
                    field.name(),
                    EXCLUDE_FIELD_PREFIX + NamingCase.toUnderlineCase(excludedField.name()),
                    null
                )
            );

            return this;
        }

        /**
         * 设置字段表达式
         *
         * @param field      字段名称
         * @param expression 表达式
         * @param args       表达式参数
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> set(String field, String expression, Object... args) {
            this.setItems.add(
                new ExpressionSetItem(
                    field,
                    expression,
                    args
                )
            );

            return this;
        }

        /**
         * 设置字段表达式
         *
         * @param field      字段枚举
         * @param expression 表达式
         * @param args       表达式参数
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> set(Enum<?> field, String expression, Object... args) {
            this.setItems.add(
                new ExpressionSetItem(
                    field.name(),
                    expression,
                    args
                )
            );

            return this;
        }

        /**
         * 设置字段值
         *
         * @param field 字段名称
         * @param value 值
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> setValue(String field, Object value) {
            this.setItems.add(
                new StandardSetItem(
                    AbstractInsertSql.this.getSqlParameterManager(),
                    field,
                    value
                )
            );

            return this;
        }

        /**
         * 设置字段值
         *
         * @param field 字段枚举
         * @param value 值
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> setValue(Enum<?> field, Object value) {
            this.setItems.add(
                new StandardSetItem(
                    AbstractInsertSql.this.getSqlParameterManager(),
                    field.name(),
                    value
                )
            );

            return this;
        }

        /**
         * 批量设置字段值
         *
         * @param fieldValueMap 字段值Map
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> setValues(Map<String, Object> fieldValueMap) {
            for (Map.Entry<String, Object> entry : fieldValueMap.entrySet()) {
                this.setValue(entry.getKey(), entry.getValue());
            }

            return this;
        }

        /**
         * 批量设置字段值
         *
         * @param fieldValueMap 字段枚举值Map
         * @return 具体实现
         */
        @Override
        public ConflictUpdateSql<C> setValuesEnum(Map<Enum<?>, Object> fieldValueMap) {
            for (Map.Entry<Enum<?>, Object> entry : fieldValueMap.entrySet()) {
                this.setValue(entry.getKey(), entry.getValue());
            }

            return this;
        }

        /**
         * 构建SQL片段
         *
         * @return SQL片段
         */
        @Override
        public String buildSqlFragment() {
            if (!AbstractInsertSql.this.ignoreConflict && this.setItems.isEmpty()) {
                throw new MybatisPlusException( "冲突更新SQL必须设置更新项");
            }

            return new CompositeSetItem(this.setItems).buildSqlFragment();
        }
    }
}
